#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <cuda.h>
#include <cuda_runtime.h>

#include "tc.h"

boid_t boid[BOID_N_BODIES];
GLuint ghoul[BOID_N_BODIES];
GLuint ghoulCount = 0;

GLuint incidentFlag = 0;
GLuint incidentTimeCount = 0;

GLfloat h_x[BOID_N_BODIES];
GLfloat h_y[BOID_N_BODIES];
GLuint h_dir[BOID_N_BODIES];
float4 h_pos[BOID_N_BODIES];

GLuint h_weight[BOID_N_BODIES];
GLuint h_status[BOID_N_BODIES];
GLuint h_nCount[BOID_N_BODIES];
float4 h_rgb[BOID_N_BODIES];

GLuint *d_dir, *d_weight, *d_status, *d_nList  , *d_nCount;
float4 *d_pos, *d_rgb;

static GLuint isInitialized = 0;

static inline GLuint getRandomDir(void)
{
	return random() % 4;
}

static inline GLuint getChangeDir(GLuint idx)
{
	GLuint look = (h_dir[idx] + 1) % 4;
	h_dir[idx] = look;

	return look;
}

static inline void boundsCheck(boid_t *me)
{
	if(me->x <= -1.0) me->look = EAST;
	if(me->x >= 1.0)  me->look = WEST;
	if(me->y <= -1.0) me->look = NORTH;
	if(me->y >= 1.0)  me->look = SOUTH;
}

extern GLuint mapBits[2048][2048];

int checkRegionOK(GLfloat x, GLfloat y)
{
	GLint viewport[4];
	GLdouble modelview[16];
	GLdouble projection[16];
	GLdouble winX, winY, winZ;
	GLdouble posX, posY, posZ;
	GLfloat c[4];

	glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
	glGetDoublev(GL_PROJECTION_MATRIX, projection);
	glGetIntegerv(GL_VIEWPORT, viewport);

	posX = (double) x;
	posY = (double) y;
	posZ = 1.0;

	gluProject(posX, posY, posZ, 
		   modelview, projection, viewport, 
		   &winX, &winY, &winZ);

	GLint wX = (GLint) winX;
	GLint wY = (GLint) winY;

	glReadPixels(wX, wY, 1, 1, GL_RGBA, GL_FLOAT, c);
	/* printf("%d %d: %f %f %f\n", wX, wY, c[0], c[1], c[2]); */

	/* if (c[0] < 0.4 && c[1] < 0.4 && c[2] < 0.4) { */
	/* 	return 1; */
	/* } */

	/* return 0; */
	return mapBits[wX][wY];
}

void initCUDA(void)
{
	cudaMalloc((void **) &d_pos, BOID_N_BODIES * sizeof(float4));
	cudaMalloc((void **) &d_rgb, BOID_N_BODIES * sizeof(float4));
	cudaMalloc((void **) &d_dir, BOID_N_BODIES * sizeof(GLuint));
	cudaMalloc((void **) &d_weight, BOID_N_BODIES * sizeof(GLuint));
	cudaMalloc((void **) &d_status, BOID_N_BODIES * sizeof(GLuint));
	cudaMalloc((void **) &d_nList , BOID_N_BODIES * sizeof(GLuint));
	cudaMalloc((void **) &d_nCount, BOID_N_BODIES * sizeof(GLuint));
}

void initBoids(void)
{
	int i, sign[] = {-1, 1};

	if (isInitialized) return;

	isInitialized = 1;

	for(i = 0; i < BOID_N_BODIES; i++) {
		boid[i].id = i;

		GLfloat _x = genNum() * sign[random() % 2];
		GLfloat _y = genNum() * sign[random() % 2];

		while (!checkRegionOK(_x, _y)) {
			_x = genNum() * sign[random() % 2];
			_y = genNum() * sign[random() % 2];
		}

		h_pos[i].x = _x;
		h_pos[i].y = _y;
		h_status[i] = BOID_HUMAN;
		h_dir[i] = random() % 4;
		h_weight[i] = 1;
		h_rgb[i].x = 0.0;
		h_rgb[i].y = 0.75;
		h_rgb[i].z = 0.0;

		boid[i].count = 0;
		boid[i].dirSync = NO_SYNC;
		boid[i].neighborCount = 0;
		boid[i].nnID = -1;
		boid[i].nnDist = -1;

		boid[i].hunger = 0;
	}

	ghoul[0] = getRandomGhoulID();
	printf("%d became a ghoul at (%f, %f)\n", ghoul[0], 
	       h_pos[ghoul[0]].x, h_pos[ghoul[0]].y);

	h_pos[ghoul[0]].x = 0.0;
	h_pos[ghoul[0]].y = 0.0;

	h_rgb[ghoul[0]].x = 1.0;
	h_rgb[ghoul[0]].y = 0.0;
	h_status[ghoul[0]] = BOID_GHOUL;

	initCUDA();
}

void plotBoid(GLuint idx)
{
	glPushMatrix();
	glColor3f(h_rgb[idx].x, h_rgb[idx].y, h_rgb[idx].z);
	GLfloat e = 0.0;
	const GLfloat halfLength  = 0.003;
	const GLfloat halfBreadth = 0.0015;

	if(!checkRegionOK(h_pos[idx].x, h_pos[idx].y)) {
		h_dir[idx] = (h_dir[idx] + 1) % 4;
	}

	if (h_status[idx] == BOID_GHOUL)
		e = 0.0002;

	glBegin(GL_TRIANGLES);
	switch(h_dir[idx]) {
	case NORTH:
		glVertex2f(h_pos[idx].x, h_pos[idx].y + (halfLength + e));
		glVertex2f(h_pos[idx].x - (halfBreadth + e), 
			   h_pos[idx].y - (halfBreadth + e));
		glVertex2f(h_pos[idx].x + (halfBreadth + e), 
			   h_pos[idx].y - (halfBreadth + e));
		break;

	case SOUTH:
		glVertex2f(h_pos[idx].x, h_pos[idx].y - (halfLength + e));
		glVertex2f(h_pos[idx].x - (halfBreadth + e), 
			   h_pos[idx].y + (halfBreadth + e));
		glVertex2f(h_pos[idx].x + (halfBreadth + e), 
			   h_pos[idx].y + (halfBreadth + e));
		break;

	case EAST:
		glVertex2f(h_pos[idx].x + (halfLength + e), h_pos[idx].y);
		glVertex2f(h_pos[idx].x - (halfBreadth + e), 
			   h_pos[idx].y - (halfBreadth + e));
		glVertex2f(h_pos[idx].x - (halfBreadth + e), 
			   h_pos[idx].y + (halfBreadth + e));
		break;

	case WEST:
		glVertex2f(h_pos[idx].x - (halfLength + e), h_pos[idx].y);
		glVertex2f(h_pos[idx].x + (halfBreadth + e), 
			   h_pos[idx].y - (halfBreadth + e));
		glVertex2f(h_pos[idx].x + (halfBreadth + e), 
			   h_pos[idx].y + (halfBreadth + e));
		break;
	}

	glEnd();
	glPopMatrix();
}

void drawAllBoids(void)
{
	int i;

	for (i = 0; i < BOID_N_BODIES; i++) {
		plotBoid(i);
	}

}

void makeBoidsUpdate(void)
{
	cudaMemcpy(d_pos, h_pos, BOID_N_BODIES * sizeof(float4), 
		   cudaMemcpyHostToDevice);
	cudaMemcpy(d_rgb, h_rgb, BOID_N_BODIES * sizeof(float4), 
		   cudaMemcpyHostToDevice);
	cudaMemcpy(d_dir, h_dir, BOID_N_BODIES * sizeof(GLuint), 
		   cudaMemcpyHostToDevice);
	cudaMemcpy(d_weight, h_weight, BOID_N_BODIES * sizeof(GLuint), 
		   cudaMemcpyHostToDevice);
	cudaMemcpy(d_status, h_status, BOID_N_BODIES * sizeof(GLuint),
		   cudaMemcpyHostToDevice);
	cudaMemcpy(d_nCount, h_nCount, BOID_N_BODIES * sizeof(GLuint),
		   cudaMemcpyHostToDevice);

	computeBoid_kernel(d_pos, d_rgb, d_dir, 
			   d_weight, d_status,
			   d_nList  , d_nCount);

	cudaMemcpy(h_pos, d_pos, BOID_N_BODIES * sizeof(GLfloat), 
		   cudaMemcpyDeviceToHost);
	cudaMemcpy(h_rgb, d_rgb, BOID_N_BODIES * sizeof(GLfloat), 
		   cudaMemcpyDeviceToHost);
	cudaMemcpy(h_dir, d_dir, BOID_N_BODIES * sizeof(GLuint), 
		   cudaMemcpyDeviceToHost);
	cudaMemcpy(h_weight, d_weight, BOID_N_BODIES * sizeof(GLuint), 
		   cudaMemcpyDeviceToHost);
	cudaMemcpy(h_status, d_status, BOID_N_BODIES * sizeof(GLuint),
		   cudaMemcpyDeviceToHost);
	cudaMemcpy(h_nCount, d_nCount, BOID_N_BODIES * sizeof(GLuint),
		   cudaMemcpyDeviceToHost);


	if(ghoulCount == BOID_N_BODIES) {
		printf("All humans are infected! End of simulation.\n");
		exit(1);
	}

}
